<template>
	<div id="slideVerify" class="slide-verify" :style="{ width: w + 'px' }" onselectstart="return false;">
		<!-- 图片加载遮蔽罩 -->
		<div :class="{ 'slider-verify-loading': loadBlock }"></div>
		<div class="canvas-container">
			<canvas ref="canvas" :width="w" :height="h"></canvas>
			<div v-if="show" class="slide-verify-refresh-icon" @click="refresh">
				<i class="iconfont icon-refresh"></i>
			</div>
			<div class="slide-verify-tips" v-if="tipWords" :class="passFlag ? 'slide-verify-sucbg' : 'slide-verify-errbg'">
				<span class="left-2">{{ tipWords }}</span>
			</div>
		</div>
		<canvas ref="block" :width="w" :height="h" class="slide-verify-block"></canvas>
		<!-- container -->
		<div class="slide-verify-slider" :class="{ 'container-active': containerCls.containerActive, 'container-success': containerCls.containerSuccess, 'container-fail': containerCls.containerFail }">
			<div class="slide-verify-slider-mask" :style="{ width: sliderBox.width }">
				<!-- slider -->
				<div class="slide-verify-slider-mask-item" :style="{ left: sliderBox.left }" @mousedown="sliderDown" @touchstart="touchStartEvent" @touchmove="touchMoveEvent" @touchend="touchEndEvent">
					<i :class="['slide-verify-slider-mask-item-icon', 'iconfont', `icon-${sliderBox.iconCls}`]"></i>
				</div>
			</div>
			<span class="slide-verify-slider-text">{{ sliderText }}</span>
		</div>
	</div>
</template>
<script lang="ts">
	import { useSlideAction } from './hooks'
	import { createImg, draw, getRandomImg, getRandomNumberByRange, throttle } from './uitl'

	export default defineComponent({
		name: 'SlideVerify',
		props: {
			// block length
			l: {
				type: Number,
				default: 42
			},
			// block radius
			r: {
				type: Number,
				default: 10
			},
			// canvas width
			w: {
				type: Number,
				default: 310
			},
			// canvas height
			h: {
				type: Number,
				default: 155
			},
			sliderText: {
				type: String,
				default: 'Slide filled right'
			},
			accuracy: {
				type: Number,
				default: 5 // 若为 -1 则不进行机器判断
			},
			show: {
				type: Boolean,
				default: true
			},
			imgs: {
				type: Array as PropType<any[]>,
				default: () => []
			},
			interval: {
				// 节流时长间隔
				type: Number,
				default: 50
			},
			tipWords: {
				type: String,
				default: ''
			},
			passFlag: {
				type: Boolean,
				default: true
			}
		},
		emits: ['success', 'again', 'fail', 'refresh'],
		setup(props, { emit }) {
			const { imgs, l, r, w, h, accuracy, interval } = props
			// 图片加载完关闭遮蔽罩
			const loadBlock = ref(true)
			const blockX = ref(0)
			const blockY = ref(0)
			// class
			const containerCls = reactive({
				containerActive: false, // container active class
				containerSuccess: false, // container success class
				containerFail: false // container fail class
			})
			// sliderMaskWidth sliderLeft
			const sliderBox = reactive({
				iconCls: 'arrow-right',
				width: '0',
				left: '0'
			})

			const block = ref<HTMLCanvasElement>()
			const blockCtx = ref<CanvasRenderingContext2D | null>()
			const canvas = ref<HTMLCanvasElement>()
			const canvasCtx = ref<CanvasRenderingContext2D | null>()
			let img: HTMLImageElement
			const { success, start, move, end, verify } = useSlideAction()

			// event
			const reset = () => {
				success.value = false
				containerCls.containerActive = false
				containerCls.containerSuccess = false
				containerCls.containerFail = false
				sliderBox.iconCls = 'arrow-right'
				sliderBox.left = '0'
				sliderBox.width = '0'

				block.value!.style.left = '0'
				canvasCtx.value?.clearRect(0, 0, w, h)
				blockCtx.value?.clearRect(0, 0, w, h)
				block.value!.width = w

				// generate img
				img.src = getRandomImg(imgs)
			}
			const refresh = () => {
				reset()
				emit('refresh')
			}

			function moveCb(moveX: number) {
				sliderBox.left = moveX + 'px'
				let blockLeft = ((w - 40 - 20) / (w - 40)) * moveX
				block.value!.style.left = blockLeft + 'px'

				containerCls.containerActive = true
				sliderBox.width = moveX + 'px'
			}

			function endCb(timestamp: number) {
				const { spliced, TuringTest } = verify(block.value!.style.left, blockX.value, accuracy)
				if (spliced) {
					if (accuracy === -1) {
						containerCls.containerSuccess = true
						sliderBox.iconCls = 'success'
						success.value = true
						emit('success', timestamp)
						return
					}
					if (TuringTest) {
						// success
						containerCls.containerSuccess = true
						sliderBox.iconCls = 'success'
						success.value = true
						emit('success', timestamp)
					} else {
						containerCls.containerFail = true
						sliderBox.iconCls = 'fail'
						emit('again')
					}
				} else {
					containerCls.containerFail = true
					sliderBox.iconCls = 'fail'
					emit('fail')
					setTimeout(() => {
						reset()
					}, 1000)
				}
			}

			const touchMoveEvent = throttle((e: TouchEvent | MouseEvent) => {
				move(w, e, moveCb)
			}, interval)

			const touchEndEvent = (e: TouchEvent | MouseEvent) => {
				end(e, endCb)
			}

			onMounted(() => {
				const _canvasCtx = canvas.value?.getContext('2d')
				const _blockCtx = block.value?.getContext('2d')
				canvasCtx.value = _canvasCtx
				blockCtx.value = _blockCtx

				img = createImg(imgs, () => {
					loadBlock.value = false
					const L = l + r * 2 + 3
					// draw block
					blockX.value = getRandomNumberByRange(L + 10, w - (L + 10))
					blockY.value = getRandomNumberByRange(10 + r * 2, h - (L + 10))
					if (_canvasCtx && _blockCtx) {
						draw(_canvasCtx, blockX.value, blockY.value, l, r, 'fill')
						draw(_blockCtx, blockX.value, blockY.value, l, r, 'clip')
						// draw image
						_canvasCtx.drawImage(img, 0, 0, w, h)
						_blockCtx.drawImage(img, 0, 0, w, h)
						// getImage
						const _y = blockY.value - r * 2 - 1
						const imgData = _blockCtx.getImageData(blockX.value, _y, L, L)
						block.value!.width = L
						_blockCtx.putImageData(imgData, 0, _y)
					}
				})

				// bindEvent
				document.addEventListener('mousemove', touchMoveEvent)
				document.addEventListener('mouseup', touchEndEvent)
			})

			// 移除全局事件
			onBeforeUnmount(() => {
				document.removeEventListener('mousemove', touchMoveEvent)
				document.removeEventListener('mouseup', touchEndEvent)
			})

			return {
				block,
				canvas,
				loadBlock,
				containerCls,
				sliderBox,
				refresh,
				sliderDown: start,
				touchStartEvent: start,
				touchMoveEvent,
				touchEndEvent
			}
		}
	})
</script>
<style scoped lang="less">
	@import url('@/assets/verify/iconfont.css');
	.position() {
		position: absolute;
		left: 0;
		top: 0;
	}
	.canvas-container {
		position: relative;
	}
	.slide-verify {
		position: relative;
		&-tips {
			position: absolute;
			left: 0;
			bottom: 0;
			width: 100%;
			height: 30px;
			line-height: 30px;
			text-align: center;
			color: #fff;
			z-index: 9999;
		}
		&-sucbg {
			background-color: rgba(92, 184, 92, 0.5);
		}
		&-errbg {
			background-color: rgba(217, 83, 79, 0.5);
		}
		&-loading {
			.position();
			right: 0;
			bottom: 0;
			background: rgba(255, 255, 255, 0.9);
			z-index: 999;
			animation: loading 1.5s infinite;
		}
		&-block {
			.position();
		}
		&-refresh-icon {
			position: absolute;
			right: 0;
			top: 0;
			width: 34px;
			height: 34px;
			cursor: pointer;
			z-index: 999;
			.iconfont {
				font-size: 34px;
				color: #fff;
			}
		}
		&-slider {
			position: relative;
			text-align: center;
			width: 100%;
			height: 40px;
			line-height: 40px;
			margin-top: 15px;
			background: #f7f9fa;
			color: #45494c;
			border: 1px solid #e4e7eb;
			&-mask {
				.position();
				height: 40px;
				border: 0 solid #1991fa;
				background: #d1e9fe;
				&-item {
					.position();
					width: 40px;
					height: 40px;
					background: #fff;
					box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
					cursor: pointer;
					transition: background 0.2s linear;
					display: flex;
					align-items: center;
					justify-content: center;
					&:hover {
						background: #1991fa;
						.iconfont {
							color: #fff;
						}
					}
					&-icon {
						line-height: 1;
						font-size: 30px;
						color: #303030;
					}
				}
			}
		}
	}
	.container-active .slide-verify-slider-mask {
		height: 38px;
		border-width: 1px;
		&-item {
			height: 38px;
			top: -1px;
			border: 1px solid #1991fa;
		}
	}
	.container-success .slide-verify-slider-mask {
		height: 38px;
		border: 1px solid #52ccba;
		background-color: #d2f4ef;
		&-item {
			height: 38px;
			top: -1px;
			border: 1px solid #52ccba;
			background-color: #52ccba !important;
		}
		.iconfont {
			color: #fff;
		}
	}
	.container-fail .slide-verify-slider-mask {
		height: 38px;
		border: 1px solid #f57a7a;
		background-color: #fce1e1;
		&-item {
			height: 38px;
			top: -1px;
			border: 1px solid #f57a7a;
			background-color: #f57a7a !important;
		}
		.iconfont {
			color: #fff;
		}
	}
	.container-active .slide-verify-slider-text,
	.container-success .slide-verify-slider-text,
	.container-fail .slide-verify-slider-text {
		display: none;
	}
	@keyframes loading {
		0% {
			opacity: 0.7;
		}
		100% {
			opacity: 9;
		}
	}
</style>
